package idx

import (
	"github.com/wuzfei/go-helper/maps"
	"github.com/zeebo/errs"
	"os"
	"sync"
	"sync/atomic"
)

var ErrFileDat = errs.Class("FileDat")

/*
	head  		idx  	 		data 		...
	 4	  44+len(name)	  		  n			...
*/

type FileDat struct {
	mux     sync.RWMutex
	head    Head
	idxMaps map[string]*Idx
	fp      *os.File
	size    int64 //打开的文件的实际大小

	writeRef *int32
	readRef  *int32

	cleaning     bool     //清理时锁定新增的key
	cleanupCache []string //清理时锁定保存新增的key
}

func NewFileDat(fp *os.File, size int64) (*FileDat, error) {
	head, idxMaps, err := loadFileIdx(fp)
	if err != nil {
		return nil, err
	}
	//最少也有写入head的四个字节
	if size < HeadLength {
		size = HeadLength
	}
	writeRef := int32(0)
	readRef := int32(0)
	return &FileDat{
		fp:       fp,
		size:     size,
		head:     head,
		idxMaps:  idxMaps,
		writeRef: &writeRef,
		readRef:  &readRef,
	}, nil
}

func (f *FileDat) GetIdx(name string) *Idx {
	f.mux.RLock()
	defer f.mux.RUnlock()
	if v, ok := f.idxMaps[name]; ok {
		return v
	}
	return nil
}

func (f *FileDat) addIdx(idx *Idx) (*Idx, error) {
	f.mux.Lock()
	defer f.mux.Unlock()
	if v, ok := f.idxMaps[string(idx.Name)]; ok {
		//标记删除
		v.SetDel()
		_, err := f.fp.WriteAt(v.Bytes(), v.IdxOffset())
		if err != nil {
			return nil, ErrFileDat.New("重复key[%s]标记删除时失败：%s", idx.Name, err)
		}
	}
	_n, err := f.fp.WriteAt(idx.Bytes(), idx.IdxOffset())
	if int64(_n) != idx.Count() || err != nil {
		return nil, ErrFileDat.New("写入idx(%d/%d)失败：%s", _n, idx.Count(), err)
	}
	f.idxMaps[string(idx.Name)] = idx
	//清理时临时记录新增的key
	if f.cleaning {
		f.cleanupCache = append(f.cleanupCache, string(idx.Name))
	}
	return idx, nil
}

func (f *FileDat) LockIdxMaps() []string {
	f.mux.Lock()
	defer f.mux.Unlock()
	f.cleaning = true
	f.cleanupCache = make([]string, 0)
	keys := maps.Keys(f.idxMaps)
	return keys
}

func (f *FileDat) UnLockIdxMaps() []string {
	f.mux.Lock()
	defer f.mux.Unlock()
	f.cleaning = false
	c := f.cleanupCache
	f.cleanupCache = f.cleanupCache[:0]
	return c
}

func (f *FileDat) Delete(key string) error {
	f.mux.Lock()
	defer f.mux.Unlock()
	if v, ok := f.idxMaps[key]; ok {
		//标记删除
		v.SetDel()
		_, err := f.fp.WriteAt(v.Bytes(), v.IdxOffset())
		if err != nil {
			return ErrFileDat.New("key[%s]标记删除时失败：%s", key, err)
		}
		delete(f.idxMaps, key)
	}
	return nil
}

func (f *FileDat) Close() error {
	f.mux.Lock()
	defer f.mux.Unlock()
	return f.fp.Close()
}

func (f *FileDat) writeRefIncr(n int32) int32 {
	if n == 0 {
		return atomic.LoadInt32(f.writeRef)
	}
	return atomic.AddInt32(f.writeRef, n)
}

func (f *FileDat) readRefIncr(n int32) int32 {
	if n == 0 {
		return atomic.LoadInt32(f.readRef)
	}
	return atomic.AddInt32(f.readRef, n)
}
